package edu.hawaii.myisern.visualization;

import java.awt.Color;
import java.awt.Dimension;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JSplitPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.filter.GraphDistanceFilter;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.DragControl;
import prefuse.controls.FocusControl;
import prefuse.controls.ToolTipControl;
import prefuse.controls.NeighborHighlightControl;
import prefuse.controls.PanControl;
import prefuse.controls.WheelZoomControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.GraphMLReader;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphLib;
import prefuse.util.PrefuseLib;
import prefuse.util.force.ForceSimulator;
import prefuse.util.ui.JForcePanel;
import prefuse.util.ui.JPrefuseApplet;
import prefuse.util.ui.JValueSlider;
import prefuse.util.ui.UILib;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;

/**
 * Small additions from Jeffrey Heer's Prefuse GraphView
 * applet implementation.  Added Mouse over to show contacts
 * associated with organization nodes.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 * @author Lisa Chen
 */
public class GraphViewApplet extends JPrefuseApplet {

  /** UID created by jheer, purpose is not clear. */
  private static final long serialVersionUID = 1L;
  /** graph will be used many times, satisfies pmd complaints. */
  private static final String graph = "graph";
  /** graph.nodes will be used many times, satisfies pmd complaints. */
  private static final String nodes = "graph.nodes";
  /** graph.edges will be used many times, satisfies pmd complaints. */
  private static final String edges = "graph.edges";
  /** draw will be used many times, satisfies pmd complaints. */
  private static final String wordDraw = "draw";

  /**
   * Initialize the applet.
   */
  public void init() {
    UILib.setPlatformLookAndFeel();
    JComponent graphview = demo("/collabGraphView.xml", "name");
    this.getContentPane().add(graphview);
  }

  /**
   * Create a Graph from a GraphML XML datafile to be rendered by the applet
   * (created by jheer).
   * 
   * @param datafile GraphML compliant XML file with data to render in GraphView.
   * @param label String node label to render onto nodes.
   * @return JComponent to the browser to render in applet.
   */
  public static JComponent demo(String datafile, String label) {
    Graph g = null;
    if (datafile == null) {
      g = GraphLib.getGrid(15, 15);
    }
    else {
      try {
        g = new GraphMLReader().readGraph(datafile);
      }
      catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
    return demo(g, label);
  }

  /**
   * Create demo of a graphView created by jheer and modified by lchen.
   * 
   * @param g Graph generated from GraphML XML document.
   * @param label String node label to render onto nodes.
   * @return JComponent to the browser to render in applet.
   */
  public static JComponent demo(Graph g, String label) {

    // create a new, empty visualization for our data
    final Visualization vis = new Visualization();
    VisualGraph vg = vis.addGraph(graph, g);
    vis.setValue(edges, null, VisualItem.INTERACTIVE, Boolean.FALSE);

    TupleSet focusGroup = vis.getGroup(Visualization.FOCUS_ITEMS);
    focusGroup.addTupleSetListener(new TupleSetListener() {
      public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) {
        for (int i = 0; i < rem.length; ++i) {
          ((VisualItem) rem[i]).setFixed(false);
        }
        for (int i = 0; i < add.length; ++i) {
          ((VisualItem) add[i]).setFixed(false);
          ((VisualItem) add[i]).setFixed(true);
        }
        vis.run(wordDraw);
      }
    });

    // set up the renderers
    LabelRenderer tr = new LabelRenderer(label);
    tr.setRoundedCorner(8, 8);
    vis.setRendererFactory(new DefaultRendererFactory(tr));

    // -- set up the actions ----------------------------------------------

    int maxhops = 4, hops = 4;
    final GraphDistanceFilter filter = new GraphDistanceFilter(graph, hops);

    ActionList draw = new ActionList();
    draw.add(filter);
    draw.add(new ColorAction(nodes, VisualItem.FILLCOLOR, ColorLib.rgb(200, 200, 255)));
    draw.add(new ColorAction(nodes, VisualItem.STROKECOLOR, 0));
    draw.add(new ColorAction(nodes, VisualItem.TEXTCOLOR, ColorLib.rgb(0, 0, 0)));
    draw.add(new ColorAction(edges, VisualItem.FILLCOLOR, ColorLib.gray(200)));
    draw.add(new ColorAction(edges, VisualItem.STROKECOLOR, ColorLib.gray(200)));

    ColorAction fill = new ColorAction(nodes, VisualItem.FILLCOLOR, ColorLib.rgb(200, 200, 255));
    fill.add("_fixed", ColorLib.rgb(255, 100, 100));
    fill.add("_highlight", ColorLib.rgb(255, 200, 125));

    ForceDirectedLayout fdl = new ForceDirectedLayout(graph);
    ForceSimulator fsim = fdl.getForceSimulator();
    fsim.getForces()[0].setParameter(0, -1.2f);

    ActionList animate = new ActionList(Activity.INFINITY);
    animate.add(fdl);
    animate.add(fill);
    animate.add(new RepaintAction());

    // finally, we register our ActionList with the Visualization.
    // we can later execute our Actions by invoking a method on our
    // Visualization, using the name we've chosen below.
    vis.putAction("draw", draw);
    vis.putAction("layout", animate);
    vis.runAfter("draw", "layout");

    // --------------------------------------------------------------------
    // STEP 4: set up a display to show the visualization

    Display display = new Display(vis);
    display.setSize(500, 500);
    display.setForeground(Color.GRAY);
    display.setBackground(Color.WHITE);

    // main display controls
    display.addControlListener(new ToolTipControl("contact"));
    display.addControlListener(new FocusControl(1));
    display.addControlListener(new DragControl());
    display.addControlListener(new PanControl());
    display.addControlListener(new ZoomControl());
    display.addControlListener(new WheelZoomControl());
    display.addControlListener(new ZoomToFitControl());
    display.addControlListener(new NeighborHighlightControl());

    display.setForeground(Color.GRAY);
    display.setBackground(Color.WHITE);

    // --------------------------------------------------------------------
    // STEP 5: launching the visualization

    // create a panel for editing force values
    final JForcePanel fpanel = new JForcePanel(fsim);

    final JValueSlider slider = new JValueSlider("Distance", 0, maxhops, hops);
    slider.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        filter.setDistance(slider.getValue().intValue());
        vis.run(wordDraw);
      }
    });
    slider.setBackground(Color.WHITE);
    slider.setPreferredSize(new Dimension(300, 30));
    slider.setMaximumSize(new Dimension(300, 30));

    Box cf = new Box(BoxLayout.Y_AXIS);
    cf.add(slider);
    cf.setBorder(BorderFactory.createTitledBorder("Connectivity Filter"));
    fpanel.add(cf);

    fpanel.add(Box.createVerticalGlue());

    // create a new JSplitPane to present the interface
    JSplitPane split = new JSplitPane();
    split.setLeftComponent(display);
    split.setRightComponent(fpanel);
    split.setOneTouchExpandable(true);
    split.setContinuousLayout(false);
    split.setDividerLocation(530);
    split.setDividerLocation(800);

    // position and fix the default focus node
    NodeItem focus = (NodeItem) vg.getNode(0);
    PrefuseLib.setX(focus, null, 400);
    PrefuseLib.setY(focus, null, 250);
    focusGroup.setTuple(focus);

    // now we run our action list and return
    return split;
  }

}
